﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "GmRotateElementAction.h"
#include "IUserTransaction.h"
#include "ISelection.h"
#include "UiCommonDialog.h"
#include "IPickPointAction.h"
#include "IUiView.h"
#include "IGraphicsArc.h"
#include "Vector3dUtils.h"
#include "AlgorithmProject.h"
#include "ITemporaryDimensionManager.h"
#include "ITemporaryDimensionContext.h"
#include "GbmpModelViewUtil.h"
#include "IInstance.h"
#include "IDocument.h"
#include "IGraphicsNodeReference.h"
#include "IGraphicsNodeGroup.h"
#include "IGraphicsLine.h"
#include "GbmpConstants.h"
#include "IReferencePlane.h"
#include "ICamera.h"
#include "IModelView.h"
#include "IElementPosition.h"
#include "IElementTransformationComponent.h"
#include "IElementUiManipulatorComponent.h"
#include "RotateUtils.h"
#include "ElementJoinUtils.h"
#include "GbmpJoinUtils.h"
#include "IPlane.h"
#include "IPositionSingleAssociatedPlaneWithTwoOffsets.h"
#include "IMultiMappingViewport.h"
#include "INotificationDetailItem.h"
#include "INotificationManager.h"
#include "INotification.h"
#include "IGraphicsElementShape.h"
#include "GbmpGraphicsNodeUtils.h"

#include "IGenericElement.h"
#include "IDrawingViewport.h"
#include "ICanvas.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;

GmRotateElementAction::GmRotateElementAction()
    : m_rotateElementStatus(RES_Not_Started)
    , m_isUserCancelled(false)
{
}

GmRotateElementAction::~GmRotateElementAction()
{
}

void GmRotateElementAction::ActionCancelled()
{
    //通知基类，会标记当前Action结束
    ActionBase::ActionCancelled();

    CleanupTempGraphicsShape();
}

void GmRotateElementAction::InitAction(IUiView* pCurrentView)
{
    //如果没有选择图元，退出
    const GraphicsNodeReferenceOwnerPtrSet& selectedItems = ISelection::Get()->GetGraphicsNodeReferences();
    if(selectedItems.size() == 0)
    {
        MarkFinishStatus(ActionFinishStatus::Successful);

        std::vector<OwnerPtr<INotificationDetailItem>> detailItems;
        INotificationManager::Get()->AddNotification(TransferOwnership(INotification::CreateNotification(NotificationTypes::InfomationNotificationType, GBMP_TR(L"请先选中对象，再进行旋转操作。"), std::move(detailItems))));
        return;
    }

    IDocument* pDoc = GetDoc();
    DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc, L"空指针pDoc",L"GDMPLab",L"2024-03-30");

    bool allHaveHost = true;
    //所有选择的图元有主体
    FOR_EACH(selectedItem, selectedItems)
    {
        IElement* pElement = pDoc->GetElement(selectedItem->GetElementId());
        if (pElement)
        {
            IGenericElement* pGenericElement = quick_cast<IGenericElement>(pElement);
            if (pGenericElement)
            {
                IDrawingViewport* pViewport = quick_cast<IDrawingViewport>(pGenericElement->GetExternalObject());
                if (pViewport)
                {
                    IMultiMappingViewport* pMultiViewportPrivate = dynamic_cast<IMultiMappingViewport*>(pViewport);
                    if (!pMultiViewportPrivate || !pMultiViewportPrivate->IsPolygonViewport())
                    {
                        MarkFinishStatus(ActionFinishStatus::Successful);
                        UiCommonDialog::ShowMessageBox(GBMP_TR(L"旋转"), GBMP_TR(L"视口不支持旋转！"), (int)UiCommonDialog::ButtonType::OK);
                        return;
                    }                    
                }
            }
        }

        IInstance* pInstance = dynamic_cast<IInstance*>(pDoc->GetElement(selectedItem->GetElementId()));
        if(!pInstance || !pInstance->GetHostInstanceId().GetIsValidId())
        {
            allHaveHost = false;
            break;
        }
    }

    if(allHaveHost)
    {
        MarkFinishStatus(ActionFinishStatus::Successful);
        UiCommonDialog::ShowMessageBox(GBMP_TR(L"旋转"), GBMP_TR(L"禁止旋转选中的图元！"), (int)UiCommonDialog::ButtonType::OK);
        return;
    }

    EnterGettingCenterPointState();
}

void GmRotateElementAction::EnterGettingCenterPointState()
{
    m_rotateElementStatus = RES_Getting_CenterPoint;

    SetPromptMessage(GBMP_TR(L"旋转选中物体 - 请输入旋转中心点"));
    StartChildAction(IPickPointAction::Create( 
        PickPointExchangeData(&m_centerPt, &m_isUserCancelled, &m_pickedPoints),
        nullptr));
}

void GmRotateElementAction::EnterGettingStartPointState()
{
    m_rotateElementStatus = RES_Getting_StartPoint;

    SetPromptMessage(GBMP_TR(L"旋转选中物体 - 请输入旋转角度起点"));

    OwnerPtr<IPickPointAction> oPickAction = IPickPointAction::Create( 
        PickPointExchangeData(&m_angleStartPt, &m_isUserCancelled, &m_pickedPoints)
        , IPickPointAction::CreateMoveCallback(&GmRotateElementAction::OnPointUpdate, this));
    oPickAction->SetFirstPoint(m_centerPt);
    StartChildAction(TransferOwnership(oPickAction));
}

void GmRotateElementAction::EnterGettingEndPointState()
{
    m_rotateElementStatus = RES_Getting_EndPoint;

    SetPromptMessage(GBMP_TR(L"旋转选中物体 - 请输入旋转角度终点"));

    OwnerPtr<IPickPointAction> oPickAction = IPickPointAction::Create( 
        PickPointExchangeData(&m_angleEndPt, &m_isUserCancelled, &m_pickedPoints)
        , IPickPointAction::CreateMoveCallback(&GmRotateElementAction::OnPointUpdate, this));
    oPickAction->SetFirstPoint(m_centerPt);

    StartChildAction(TransferOwnership(oPickAction));
}

void GmRotateElementAction::OnChildActionFinished(IUiView* pCurrentView, const ActionOutput& childActionReturnParam)
{
    if(m_rotateElementStatus == RES_Getting_CenterPoint)
    {
        if(m_isUserCancelled)
        {
            // 如果用户在指定第一个点时就选择退出，那么就退出整个工具
            CleanupTempGraphicsShape();
            MarkFinishStatus(ActionFinishStatus::Successful);
            UpdateView();
            return;
        }
        EnterGettingStartPointState();
    }
    else if (m_rotateElementStatus == RES_Getting_StartPoint)
    {
        if(m_isUserCancelled)
        {
            // 如果用户在指定角度起始点时选择退出，那么先退到指定中心点的状态
            CleanupTempGraphicsShape();
            m_pickedPoints.pop_back();
            EnterGettingCenterPointState();
            UpdateView();
            return;
        }

        if (Vector3dUtils::IsEqual(m_angleStartPt,m_centerPt))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"旋转选中物体"), GBMP_TR(L"中心点和角度起始点不能重合"), (int)UiCommonDialog::ButtonType::OK);
            m_pickedPoints.pop_back();
            EnterGettingStartPointState();
            return;
        }

        EnterGettingEndPointState();
    }
    else if (m_rotateElementStatus == RES_Getting_EndPoint)
    {
        if(m_isUserCancelled)
        {
            // 如果用户在指定角度终止点时选择退出，那么先退到指定角度起始点的状态
            CleanupTempGraphicsShape();
            m_pickedPoints.pop_back();
            EnterGettingStartPointState();
            UpdateView();
            return;
        }

        if (Vector3dUtils::IsEqual(m_angleEndPt,m_centerPt) || Vector3dUtils::IsEqual(m_angleEndPt,m_angleStartPt))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"旋转选中物体"), GBMP_TR(L"角度终止点不能和中心点或角度起始点重合"), (int)UiCommonDialog::ButtonType::OK);
            m_pickedPoints.pop_back();
            EnterGettingEndPointState();
            return;
        }

        IDocument* pDoc = GetDoc();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc, L"空指针pDoc",L"GDMPLab",L"2024-03-30");
        DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView, L"空指针pCurrentView",L"GDMPLab",L"2024-03-30");
        IModelView* pModelView = pCurrentView->GetModelView();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pModelView, L"空指针pCurrentView",L"GDMPLab",L"2024-03-30");

        const GraphicsNodeReferenceOwnerPtrSet& selectedRefs = ISelection::Get()->GetGraphicsNodeReferences();
        ElementIdArray selectedElements;
        FOR_EACH(item, selectedRefs)
        {
            ElementId elementId = item->GetElementId();
            const IElement* pElement = pDoc->GetElement(elementId);
            if (!pElement)
            {
                continue;
            }

            std::wstring cannotRotatedExplanation;
            const IElementTransformationComponent *pElementTransformationComponent = pElement->GetElementTransformationComponent();
            if (pElementTransformationComponent && !pElementTransformationComponent->CanBeRotated(&cannotRotatedExplanation))
            {
                continue;
            }

            IElement* pInstanceElement = pDoc->GetElement(elementId);
            if (IsA<IInstance>(pInstanceElement))
            {
                IInstance *pInstance = quick_cast<IInstance>(pInstanceElement);
                //不旋转有主体的实体
                if (pInstance->GetHostInstanceId().IsValid())
                {
                    continue;
                }

                //立面视图不旋转连接到平面的实体
                if (Vector3dUtils::IsPerpendicular(pModelView->GetViewDirection(), Vector3d::UnitZ))
                {
                    const IElementPosition* pElemPosBehavior = pInstance->GetElementPosition();
                    if (!pElemPosBehavior)
                    {
                        continue;
                    }
                    const IPositionAssociatedPlane* pPositionSingleAssociatedPlan = pElemPosBehavior->GetPositionAssociatedPlane();
                    if (const IPositionSingleAssociatedPlaneWithTwoOffsets* posHelper = quick_cast<const IPositionSingleAssociatedPlaneWithTwoOffsets>(pPositionSingleAssociatedPlan))
                    {
                        if (posHelper->IsStayHorizontal())
                        {
                            continue;
                        }
                    }
                }                  
            }

            selectedElements.push_back(elementId);
        }

        Vector3d adjustedAngleStartPt = AdjustRotationAnglePoint(pCurrentView, m_angleStartPt);
        Vector3d adjustedAngleEndPt = AdjustRotationAnglePoint(pCurrentView, m_angleEndPt);

        Vector3d angleVec1 = adjustedAngleStartPt - m_centerPt;
        angleVec1.Normalize();
        Vector3d angleVec2 = adjustedAngleEndPt - m_centerPt;
        angleVec2.Normalize();
        double angleInRadian = Vector3dUtils::GetAngle(angleVec1, angleVec2);

        //旋转轴向量
        Vector3d rotationAxis = angleVec1^angleVec2;
        //如果旋转轴向量为空，使用model view的工作平面法向
        if (Vector3dUtils::IsEqual(rotationAxis, Vector3d()))
        {
            OwnerPtr<IPlane> opPlane = pModelView->GetWorkPlane();
            DBG_WARN_AND_RETURN_VOID_UNLESS(opPlane, L"空指针opPlane",L"GDMPLab",L"2024-03-30");
            rotationAxis = opPlane->GetNormal();
        }

        OwnerPtr<IUserTransaction> opUt = IUserTransaction::Create(pDoc, GBMP_TR(L"旋转选中物体"), true);
        DBG_WARN_AND_RETURN_VOID_UNLESS(opUt.get(), L"空指针opUt",L"GDMPLab",L"2024-03-30");

        std::wstring strError;

        // step 1:先断开原来的连接
        for (int i = 0; i < selectedElements.size(); i++)
        {
            ElementJoinUtils::DeleteAllElementJoins(pDoc, selectedElements[i]);
        }

        // step 2:进行旋转
        Bool result = RotateUtils::RotateElements(
            pDoc, 
            selectedElements, 
            m_centerPt, 
            rotationAxis, 
            angleInRadian, 
            &strError);

        // step 3:建立新的连接关系
        for (int i = 0; i < selectedElements.size(); i++)
        {
            GbmpJoinUtils::CreateJoinsAndCuts(pDoc, selectedElements[i]);
        }

        if (result)
        {
            opUt->Commit();
        }
        else
        {
            opUt->Rollback();
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"旋转选中物体"), strError, (int)UiCommonDialog::ButtonType::OK);
        }

        CleanupTempGraphicsShape();
        UpdateView();
        m_pickedPoints.clear();
        m_pickedPoints.push_back(m_centerPt);
        // 准备在同一个中心点再次旋转
        EnterGettingStartPointState();
    }
    else
    {
        DBG_WARN(L"未知的m_rotateElementStatus状态",L"GDMPLab",L"2024-03-30");
    }
}

void GmRotateElementAction::OnPointUpdate(IUiView* pCurrentView, const gcmp::Vector3d& pos, const gcmp::Vector3d& mousePt)
{
    UpdateTempGRep(pCurrentView, pos);
    UpdateView();
}

void GmRotateElementAction::UpdateTempGRep(IUiView* pCurrentView, const gcmp::Vector3d& pos)
{
    if(m_rotateElementStatus != RES_Getting_StartPoint && m_rotateElementStatus != RES_Getting_EndPoint)
    {
        return;
    }

    ICanvas *pCanvas = pCurrentView->GetCanvas();
    DBG_WARN_AND_RETURN_VOID_UNLESS(pCanvas, L"pCanvas为空",L"GDMPLab",L"2024-03-30");
    const ICamera* pCamera = pCanvas->GetCamera();
    DBG_WARN_AND_RETURN_VOID_UNLESS(pCamera, L"pCamera为空",L"GDMPLab",L"2024-03-30");

    // 计算绘制标识线的平面
    Vector3d yDir(pCamera->GetUpDirection());
    Vector3d xDir(pCamera->GetDirection().Cross(pCamera->GetUpDirection()));

    if(m_rotateElementStatus == RES_Getting_StartPoint)
    {
        Vector3d adjustedPos = AdjustRotationAnglePoint(pCurrentView, pos);
        // 绘制中心点位置和中心点到角度起始点的直线
        gcmp::OwnerPtr<IGraphicsElementShape> upGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
        DBG_WARN_AND_RETURN_VOID_UNLESS(upGRep, L"upGRep为空",L"GDMPLab",L"2024-03-30");
        OwnerPtr<IGraphicsNodeGroup> upTempGpGRep = GbmpGraphicsNodeUtils::TransferGRepToGGroup(TransferOwnership(upGRep));
        DBG_WARN_AND_RETURN_VOID_UNLESS(upTempGpGRep, L"upTempGpGRep为空",L"GDMPLab",L"2024-03-30");
        upTempGpGRep->AddChild(IGraphicsArc::CreateByCenterAndRadius(m_centerPt, xDir, yDir, 200.0, 0, Constants::MATH_PI * 2, true));


        Vector3d startLineDir = adjustedPos - m_centerPt;
        startLineDir.Normalize();
        upTempGpGRep->AddChild(IGraphicsLine::Create(m_centerPt, adjustedPos + startLineDir * 1000.0));

        DrawTempGraphicsNodeGroup(TransferOwnership(upTempGpGRep));
    }
    else if(m_rotateElementStatus == RES_Getting_EndPoint)
    {
        Vector3d adjustedPos = AdjustRotationAnglePoint(pCurrentView, pos);
        Vector3d adjustedAngleStartPt = AdjustRotationAnglePoint(pCurrentView, m_angleStartPt);

        // 绘制中心点位置
        gcmp::OwnerPtr<IGraphicsElementShape> upGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
        DBG_WARN_AND_RETURN_VOID_UNLESS(upGRep, L"upGRep为空",L"GDMPLab",L"2024-03-30");
        OwnerPtr<IGraphicsNodeGroup> upTempGpGRep = GbmpGraphicsNodeUtils::TransferGRepToGGroup(TransferOwnership(upGRep));
        DBG_WARN_AND_RETURN_VOID_UNLESS(upTempGpGRep, L"upTempGpGRep为空",L"GDMPLab",L"2024-03-30");
        upTempGpGRep->AddChild(IGraphicsArc::CreateByCenterAndRadius(m_centerPt, xDir, yDir, 200.0, 0, Constants::MATH_PI * 2, true));

        // 绘制旋转临时标注
        if (upTempGpGRep)
        {
            Vector3d startDir = m_angleStartPt - m_centerPt;
            double radius = startDir.Normalize();
            Vector3d endDir = m_angleEndPt - m_centerPt;
            endDir.Normalize();
            Vector3d endPt = m_centerPt + endDir * radius;

            OwnerPtr<ITemporaryDimensionContext> opCtx = ITemporaryDimensionContext::CreateRotationContext(
                m_centerPt, m_angleStartPt, endPt, GbmpModelViewUtil::GetWorkPlaneCoordinate(pCurrentView->GetModelView()));
            upTempGpGRep->AddChild(ITemporaryDimensionManager::Get()->GenerateTemporaryDimensionGraphics(opCtx.get()));
        }


        DrawTempGraphicsNodeGroup(TransferOwnership(upTempGpGRep));
    }
    else
    {
        return;
    }
}

Vector3d GmRotateElementAction::AdjustRotationAnglePoint(IUiView* pCurrentView, const Vector3d& ptToAdjust)
{
    DBG_WARN_AND_RETURN_UNLESS(pCurrentView != nullptr, Vector3d::Zero, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
    OwnerPtr<IPlane> opPlane = pCurrentView->GetModelView()->GetWorkPlane();

    // 把点投影到与工作平面平行，且经过旋转中心点的平面上
    Vector3d adjustedPt;
    Vector2d uvParameter;
    AlgorithmProject::Project(ptToAdjust, opPlane.get(), uvParameter, adjustedPt);
    return adjustedPt;
}
